home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / backend.py < prev    next >
Encoding:
Python Source  |  2008-10-08  |  14.9 KB  |  406 lines

  1. #
  2. # backend.py: interface for backends
  3. #
  4. # Copyright (C) 2008 Canonical Ltd.
  5. #
  6. #    This program is free software: you can redistribute it and/or modify
  7. #    it under the terms of the GNU General Public License version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import os
  20. import re
  21. import stat
  22. from stat import *
  23. import sys
  24. import ufw.util
  25. from ufw.util import warn, debug
  26. from ufw.common import UFWError, config_dir, UFWRule
  27. import ufw.applications
  28.  
  29. class UFWBackend:
  30.     '''Interface for backends'''
  31.     def __init__(self, name, d, extra_files={}):
  32.         self.defaults = {}
  33.         self.name = name
  34.         self.dryrun = d
  35.         self.rules = []
  36.         self.rules6 = []
  37.  
  38.         self.files = {'defaults': os.path.join(config_dir, 'default/ufw'),
  39.                       'conf': os.path.join(config_dir, 'ufw/ufw.conf'),
  40.                       'apps': os.path.join(config_dir, 'ufw/applications.d') }
  41.         self.files.update(extra_files)
  42.  
  43.         self.do_checks = True
  44.         try:
  45.             self._do_checks()
  46.             self._get_defaults()
  47.             self._read_rules()
  48.         except Exception:
  49.             raise
  50.  
  51.         self.profiles = ufw.applications.get_profiles(self.files['apps'])
  52.  
  53.     def _is_enabled(self):
  54.         if self.defaults.has_key('enabled') and \
  55.            self.defaults['enabled'] == 'yes':
  56.             return True
  57.         return False
  58.  
  59.     def use_ipv6(self):
  60.         if self.defaults.has_key('ipv6') and \
  61.            self.defaults['ipv6'] == 'yes' and \
  62.            os.path.exists("/proc/sys/net/ipv6"):
  63.             return True
  64.         return False
  65.  
  66.     def _do_checks(self):
  67.         '''Perform basic security checks:
  68.         is setuid or setgid (for non-Linux systems)
  69.         checks that script is owned by root
  70.         checks that every component in absolute path are owned by root
  71.         checks that every component of absolute path are not a symlink
  72.         warn if script is group writable
  73.         warn if part of script path is group writable
  74.  
  75.         Doing this at the beginning causes a race condition with later
  76.         operations that don't do these checks.  However, if the user running
  77.         this script is root, then need to be root to exploit the race
  78.         condition (and you are hosed anyway...)
  79.         '''
  80.  
  81.         if not self.do_checks:
  82.             err_msg = _("Checks disabled")
  83.             warn(err_msg)
  84.             return True
  85.  
  86.         # Not needed on Linux, but who knows the places we will go...
  87.         if os.getuid() != os.geteuid():
  88.             err_msg = _("ERROR: this script should not be SUID")
  89.             raise UFWError(err_msg)
  90.         if os.getgid() != os.getegid():
  91.             err_msg = _("ERROR: this script should not be SGID")
  92.             raise UFWError(err_msg)
  93.         uid = os.getuid()
  94.  
  95.         if uid != 0:
  96.             err_msg = _("You need to be root to run this script")
  97.             raise UFWError(err_msg)
  98.  
  99.         # Use these so we only warn once
  100.         warned_world_write = {}
  101.         warned_group_write = {}
  102.         warned_owner = {}
  103.  
  104.         pat = re.compile(r'^\.')
  105.  
  106.         profiles = []
  107.         if not os.path.isdir(self.files['apps']):
  108.             warn_msg = _("'%s' does not exist") % (self.files['apps'])
  109.             warn(warn_msg)
  110.         else:
  111.             for profile in os.listdir(self.files['apps']):
  112.                 profiles.append(os.path.join(self.files['apps'], profile))
  113.  
  114.         for path in self.files.values() + [ os.path.abspath(sys.argv[0]) ] + \
  115.                 profiles:
  116.             while True:
  117.                 debug("Checking " + path)
  118.                 if pat.search(os.path.basename(path)):
  119.                     err_msg = _("found hidden directory in path: %s") % (path)
  120.                     raise UFWError(err_msg)
  121.  
  122.                 if path == self.files['apps'] and \
  123.                            not os.path.isdir(self.files['apps']):
  124.                     break
  125.  
  126.                 try:
  127.                     statinfo = os.stat(path)
  128.                     mode = statinfo[ST_MODE]
  129.                 except OSError, e:
  130.                     err_msg = _("Couldn't stat '%s'") % (path)
  131.                     raise UFWError(err_msg)
  132.                 except Exception:
  133.                     raise
  134.  
  135.                 if os.path.islink(path):
  136.                     err_msg = _("found symbolic link in path: %s") % (path)
  137.                     raise UFWError(err_msg)
  138.                 if statinfo.st_uid != 0 and not warned_owner.has_key(path):
  139.                     warn_msg = _("uid is %s but '%s' is owned by %s") % \
  140.                                 (str(uid), path, str(statinfo.st_uid))
  141.                     warn(warn_msg)
  142.                     warned_owner[path] = True
  143.                 if mode & S_IWOTH and not warned_world_write.has_key(path):
  144.                     warn_msg = _("%s is world writable!") % (path)
  145.                     warn(warn_msg)
  146.                     warned_world_write[path] = True
  147.                 if mode & S_IWGRP and not warned_group_write.has_key(path):
  148.                     warn_msg = _("%s is group writable!") % (path)
  149.                     warn(warn_msg)
  150.                     warned_group_write[path] = True
  151.  
  152.                 if path == "/":
  153.                     break
  154.  
  155.                 path = os.path.dirname(path)
  156.                 if not path:
  157.                     raise
  158.  
  159.         for f in self.files:
  160.             if f != 'apps' and not os.path.isfile(self.files[f]):
  161.                 err_msg = _("'%s' file '%s' does not exist") % (f, \
  162.                                                                 self.files[f])
  163.                 raise UFWError(err_msg)
  164.  
  165.     def _get_defaults(self):
  166.         '''Get all settings from defaults file'''
  167.         self.defaults = {}
  168.         for f in [self.files['defaults'], self.files['conf']]:
  169.             try:
  170.                 orig = ufw.util.open_file_read(f)
  171.             except Exception:
  172.                 err_msg = _("Couldn't open '%s' for reading") % (f)
  173.                 raise UFWError(err_msg)
  174.             pat = re.compile(r'^\w+="?\w+"?')
  175.             for line in orig:
  176.                 if pat.search(line):
  177.                     tmp = re.split(r'=', line.strip())
  178.                     self.defaults[tmp[0].lower()] = tmp[1].lower().strip('"\'')
  179.  
  180.             orig.close()
  181.  
  182.     def set_default(self, f, opt, value):
  183.         '''Sets option in defaults file'''
  184.         if not re.match(r'^[\w_]+$', opt):
  185.             err_msg = _("Invalid option")
  186.             raise UFWError(err_msg)
  187.  
  188.         try:
  189.             fns = ufw.util.open_files(f)
  190.         except Exception:
  191.             raise
  192.         fd = fns['tmp']
  193.  
  194.         pat = re.compile(r'^' + opt + '=')
  195.         for line in fns['orig']:
  196.             if pat.search(line):
  197.                 os.write(fd, opt + "=" + value + "\n")
  198.             else:
  199.                 os.write(fd, line)
  200.  
  201.         ufw.util.close_files(fns)
  202.  
  203.         # Now that the files are written out, update value in memory
  204.         self.defaults[opt.lower()] = value.lower().strip('"\'')
  205.  
  206.     def set_default_application_policy(self, policy):
  207.         '''Sets default application policy of firewall'''
  208.         if not self.dryrun:
  209.             if policy == "allow":
  210.                 self.set_default(self.files['defaults'], \
  211.                                             "DEFAULT_APPLICATION_POLICY", \
  212.                                             "\"ACCEPT\"")
  213.             elif policy == "deny":
  214.                 self.set_default(self.files['defaults'], \
  215.                                             "DEFAULT_APPLICATION_POLICY", \
  216.                                             "\"DROP\"")
  217.             elif policy == "skip":
  218.                 self.set_default(self.files['defaults'], \
  219.                                             "DEFAULT_APPLICATION_POLICY", \
  220.                                             "\"SKIP\"")
  221.             else:
  222.                 err_msg = _("Unsupported policy '%s'") % (policy)
  223.                 raise UFWError(err_msg)
  224.  
  225.         rstr = _("Default application policy changed to '%s'\n") % (policy)
  226.  
  227.         return rstr
  228.  
  229.     def get_app_rules_from_template(self, template):
  230.         '''Return a list of UFWRules based on the template rule'''
  231.         rules = []
  232.         profile_names = self.profiles.keys()
  233.  
  234.         if template.dport in profile_names and template.sport in profile_names:
  235.             dports = ufw.applications.get_ports(self.profiles[template.dport])
  236.             sports = ufw.applications.get_ports(self.profiles[template.sport])
  237.             for i in dports:
  238.                 tmp = template.dup_rule()
  239.                 tmp.dapp = ""
  240.                 tmp.set_port("any", "src")
  241.                 try:
  242.                     (port, proto) = ufw.util.parse_port_proto(i)
  243.                     tmp.set_protocol(proto)
  244.                     tmp.set_port(port, "dst")
  245.                 except Exception:
  246.                     raise
  247.  
  248.                 tmp.dapp = template.dapp
  249.  
  250.                 if template.dport == template.sport:
  251.                     # Just use the same ports as dst for src when they are the
  252.                     # same to avoid duplicate rules
  253.                     tmp.sapp = ""
  254.                     try:
  255.                         (port, proto) = ufw.util.parse_port_proto(i)
  256.                         tmp.set_protocol(proto)
  257.                         tmp.set_port(port, "src")
  258.                     except Exception:
  259.                         raise
  260.  
  261.                     tmp.sapp = template.sapp
  262.                     rules.append(tmp)
  263.                 else:
  264.                     for j in sports:
  265.                         rule = tmp.dup_rule()
  266.                         rule.sapp = ""
  267.                         try:
  268.                             (port, proto) = ufw.util.parse_port_proto(j)
  269.                             rule.set_protocol(proto)
  270.                             rule.set_port(port, "src")
  271.                         except Exception:
  272.                             raise
  273.  
  274.                         if rule.protocol == "any":
  275.                             rule.set_protocol(tmp.protocol)
  276.  
  277.                         rule.sapp = template.sapp
  278.                         rules.append(rule)
  279.         elif template.sport in profile_names:
  280.             for p in ufw.applications.get_ports(self.profiles[template.sport]):
  281.                 rule = template.dup_rule()
  282.                 rule.sapp = ""
  283.                 try:
  284.                     (port, proto) = ufw.util.parse_port_proto(p)
  285.                     rule.set_protocol(proto)
  286.                     rule.set_port(port, "src")
  287.                 except Exception:
  288.                     raise
  289.  
  290.                 rule.sapp = template.sapp
  291.                 rules.append(rule)
  292.         elif template.dport in profile_names:
  293.             for p in ufw.applications.get_ports(self.profiles[template.dport]):
  294.                 rule = template.dup_rule()
  295.                 rule.dapp = ""
  296.                 try:
  297.                     (port, proto) = ufw.util.parse_port_proto(p)
  298.                     rule.set_protocol(proto)
  299.                     rule.set_port(port, "dst")
  300.                 except Exception:
  301.                     raise
  302.  
  303.                 rule.dapp = template.dapp
  304.                 rules.append(rule)
  305.  
  306.         if len(rules) < 1:
  307.             err_msg = _("No rules found for application profile")
  308.             raise UFWError(err_msg)
  309.  
  310.         return rules
  311.  
  312.     def update_app_rule(self, profile):
  313.         '''Update rule for profile in place. Returns result string and bool
  314.            on whether or not the profile is used in the current ruleset.
  315.         '''
  316.         updated_rules = []
  317.         updated_rules6 = []
  318.         last_tuple = ""
  319.         rstr = ""
  320.         updated_profile = False
  321.  
  322.         # Remember, self.rules is from user[6].rules, and not the running
  323.         # firewall.
  324.         for r in self.rules + self.rules6:
  325.             if r.dapp == profile or r.sapp == profile:
  326.                 # We assume that the rules are in app rule order. Specifically,
  327.                 # if app rule has multiple rules, they are one after the other.
  328.                 # If the rule ordering changes, the below will have to change.
  329.                 tuple = r.get_app_tuple()
  330.                 if tuple == last_tuple:
  331.                     # Skip the rule if seen this tuple already (ie, it is part
  332.                     # of a known tuple).
  333.                     continue
  334.                 else:
  335.                     # Have a new tuple, so find and insert new app rules here
  336.                     template = r.dup_rule()
  337.                     template.set_protocol("any")
  338.                     if template.dapp != "":
  339.                         template.set_port(template.dapp, "dst")
  340.                     if template.sapp != "":
  341.                         template.set_port(template.sapp, "src")
  342.                     try:
  343.                         new_app_rules = self.get_app_rules_from_template(\
  344.                                           template)
  345.                     except Exception:
  346.                         raise
  347.  
  348.                     for new_r in new_app_rules:
  349.                         new_r.normalize()
  350.                         if new_r.v6:
  351.                             updated_rules6.append(new_r)
  352.                         else:
  353.                             updated_rules.append(new_r)
  354.  
  355.                     last_tuple = tuple
  356.                     updated_profile = True
  357.             else:
  358.                 if r.v6:
  359.                     updated_rules6.append(r)
  360.                 else:
  361.                     updated_rules.append(r)
  362.  
  363.         if updated_profile:
  364.             self.rules = updated_rules
  365.             self.rules6 = updated_rules6
  366.             rstr += _("Rules updated for profile '%s'") % (profile)
  367.  
  368.             try:
  369.                 self._write_rules(False) # ipv4
  370.                 self._write_rules(True) # ipv6
  371.             except Exception:
  372.                 err_msg = _("Couldn't update application rules")
  373.                 raise UFWError(err_msg)
  374.  
  375.         return (rstr, updated_profile)
  376.  
  377.     # API overrides
  378.     def get_loglevel(self):
  379.         raise UFWError("UFWBackend.get_loglevel: need to override")
  380.  
  381.     def set_loglevel(self, level):
  382.         raise UFWError("UFWBackend.set_loglevel: need to override")
  383.  
  384.     def get_default_policy(self):
  385.         raise UFWError("UFWBackend.get_default_policy: need to override")
  386.  
  387.     def set_default_policy(self, policy):
  388.         raise UFWError("UFWBackend.set_default_policy: need to override")
  389.  
  390.     def get_status(self):
  391.         raise UFWError("UFWBackend.get_status: need to override")
  392.  
  393.     def set_rule(self, rule, allow_reload):
  394.         raise UFWError("UFWBackend.set_rule: need to override")
  395.  
  396.     def start_firewall(self):
  397.         raise UFWError("UFWBackend.start_firewall: need to override")
  398.  
  399.     def stop_firewall(self):
  400.         raise UFWError("UFWBackend.stop_firewall: need to override")
  401.  
  402.     def get_app_rules_from_system(self, template, v6):
  403.         raise UFWError("UFWBackend.get_app_rules_from_system: need to " + \
  404.                        "override")
  405.  
  406.